// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef REMOTING_HOST_WIN_RDP_CLIENT_WINDOW_H_
#define REMOTING_HOST_WIN_RDP_CLIENT_WINDOW_H_

#include "base/memory/raw_ptr.h"

// Must be included before <atlapp.h>.
#include "base/win/atl.h"  // NOLINT(build/include_order)

#include <atlapp.h>
#include <atlcrack.h>
#include <wrl/client.h>

#include "base/memory/ref_counted.h"
#include "base/timer/timer.h"
#include "base/win/atl.h"
#include "net/base/ip_endpoint.h"
#include "remoting/host/base/screen_resolution.h"
// The following header was generated by Visual Studio. We had to check it in
// due to a bug in VS2013. See crbug.com/318952 for details.
#include "remoting/host/win/com_imported_mstscax.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"

namespace remoting {

// RdpClientWindow is used to establish a connection to the given RDP endpoint.
// It is a GUI window class that hosts Microsoft RDP ActiveX control, which
// takes care of handling RDP properly. RdpClientWindow must be used only on
// a UI thread.
class RdpClientWindow
    : public CWindowImpl<RdpClientWindow, CWindow, CFrameWinTraits>,
      public IDispEventImpl<1, RdpClientWindow,
                            &__uuidof(mstsc::IMsTscAxEvents),
                            &__uuidof(mstsc::__MSTSCLib), 1, 0> {
 public:
  // Receives connect/disconnect notifications. The notifications can be
  // delivered after RdpClientWindow::Connect() returned success.
  //
  // RdpClientWindow guarantees that OnDisconnected() is the last notification
  // the event handler receives. OnDisconnected() is guaranteed to be called
  // only once.
  class EventHandler {
   public:
    virtual ~EventHandler() {}

    // Invoked when the RDP control has established a connection.
    virtual void OnConnected() = 0;

    // Invoked when the RDP control has been disconnected from the RDP server.
    // This includes both graceful shutdown and any fatal error condition.
    //
    // Once RdpClientWindow::Connect() returns success the owner of the
    // |RdpClientWindow| object must keep it alive until OnDisconnected() is
    // called.
    //
    // OnDisconnected() should not delete |RdpClientWindow| object directly.
    // Instead it should post a task to delete the object. The ActiveX code
    // expects the window be alive until the currently handled window message is
    // completely processed.
    virtual void OnDisconnected() = 0;
  };

  DECLARE_WND_CLASS(L"RdpClientWindow")

  // Specifies the endpoint to connect to and passes the event handler pointer
  // to be notified about connection events.
  RdpClientWindow(const net::IPEndPoint& server_endpoint,
                  const std::string& terminal_id,
                  EventHandler* event_handler);
  ~RdpClientWindow() override;

  // Creates the window along with the ActiveX control and initiates the
  // connection. |resolution| specifies resolution of the screen. Returns false
  // if an error occurs.
  bool Connect(const ScreenResolution& resolution);

  // Initiates shutdown of the connection. The caller must not delete |this|
  // until it receives OnDisconnected() notification.
  void Disconnect();

  // Emulates pressing Ctrl+Alt+End combination that is translated to Secure
  // Attention Sequence by the ActiveX control.
  void InjectSas();

  // Change the resolution of the desktop.
  void ChangeResolution(const ScreenResolution& resolution);

 private:
  typedef IDispEventImpl<1, RdpClientWindow,
                         &__uuidof(mstsc::IMsTscAxEvents),
                         &__uuidof(mstsc::__MSTSCLib), 1, 0> RdpEventsSink;

  // Handled window messages.
  BEGIN_MSG_MAP_EX(RdpClientWindow)
    MSG_WM_CLOSE(OnClose)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()

  // Requests the RDP ActiveX control to close the connection gracefully.
  void OnClose();

  // Creates the RDP ActiveX control, configures it, and initiates an RDP
  // connection to |server_endpoint_|.
  LRESULT OnCreate(CREATESTRUCT* create_struct);

  // Releases the RDP ActiveX control interfaces.
  void OnDestroy();

  BEGIN_SINK_MAP(RdpClientWindow)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 2,
                  &RdpClientWindow::OnConnected)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 3,
                  &RdpClientWindow::OnLoginComplete)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 4,
                  &RdpClientWindow::OnDisconnected)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 10,
                  &RdpClientWindow::OnFatalError)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 15,
                  &RdpClientWindow::OnConfirmClose)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 18,
                  &RdpClientWindow::OnAuthenticationWarningDisplayed)
    SINK_ENTRY_EX(1, __uuidof(mstsc::IMsTscAxEvents), 19,
                  &RdpClientWindow::OnAuthenticationWarningDismissed)
  END_SINK_MAP()

  // mstsc::IMsTscAxEvents notifications.
  STDMETHOD(OnAuthenticationWarningDisplayed)();
  STDMETHOD(OnAuthenticationWarningDismissed)();
  STDMETHOD(OnConnected)();
  STDMETHOD(OnLoginComplete)();
  STDMETHOD(OnDisconnected)(long reason);
  STDMETHOD(OnFatalError)(long error_code);
  STDMETHOD(OnConfirmClose)(VARIANT_BOOL* allow_close);

  int LogOnCreateError(HRESULT error);

  // Wrappers for the event handler's methods that make sure that
  // OnDisconnected() is the last notification delivered and is delivered
  // only once.
  void NotifyConnected();
  void NotifyDisconnected();

  // Updates the desktop using |screen_resolution_| if resizing is supported.
  HRESULT UpdateDesktopResolution();

  // Attempts to reapply the requested screen resolution.  This method is used
  // to workaround the inconsistent behavior seen by the RDP API which can fail
  // for an indeterminate amount of time (i.e. a few seconds) after the user has
  // logged into the session.  In practice retrying after ~100ms will succeed,
  // but we will use |apply_resolution_timer_| to try a few times if the machine
  // is under load.
  void ReapplyDesktopResolution();

  // Invoked to report connect/disconnect events.
  raw_ptr<EventHandler> event_handler_;

  // Contains the requested dimensions of the screen.
  remoting::ScreenResolution screen_resolution_;

  // Used for applying resolution changes after a timeout.
  base::RepeatingTimer apply_resolution_timer_;

  // Tracks the number of resolution change retries.
  int apply_resolution_attempts_ = 0;

  // The endpoint to connect to.
  net::IPEndPoint server_endpoint_;

  // The terminal ID assigned to this connection.
  std::string terminal_id_;

  // Our RDP session always starts logged out (i.e. at the logon screen), some
  // functions do not work until the user has logged in so we want to track that
  // event.  We don't support logging out so this value should never revert from
  // true to false.
  bool user_logged_in_ = false;

  // Interfaces exposed by the RDP ActiveX control.
  Microsoft::WRL::ComPtr<mstsc::IMsRdpClient> client_;
  Microsoft::WRL::ComPtr<mstsc::IMsRdpClient9> client_9_;
  Microsoft::WRL::ComPtr<mstsc::IMsRdpClientAdvancedSettings> client_settings_;

  // Used to cancel modal dialog boxes shown by the RDP control.
  class WindowHook;
  scoped_refptr<WindowHook> window_activate_hook_;
};

}  // namespace remoting

#endif  // REMOTING_HOST_WIN_RDP_CLIENT_WINDOW_H_
